{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "2f7c6768",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import re\n",
    "import os\n",
    "import warnings\n",
    "from sklearn.model_selection import train_test_split, GridSearchCV\n",
    "from sklearn.ensemble import GradientBoostingClassifier, BaggingClassifier, StackingClassifier\n",
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.naive_bayes import GaussianNB\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.metrics import roc_auc_score\n",
    "# 忽略警告\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e14ceab",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当前工作目录： F:/H/H\n"
     ]
    }
   ],
   "source": [
    "# 当前工作目录(修改为你的工作目录)\n",
    "cwd = 'F:/H/H'\n",
    "# 创建必要的目录\n",
    "os.makedirs(f'{cwd}/tmp', exist_ok=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "d0b1cb36",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1. 数据预处理函数\n",
    "def data_process(samples_file, logs_file, need_label=True, out_file=None):\n",
    "    \"\"\"\n",
    "    用于获取样本特征，并将结果保存写出\n",
    "    :param samples_file: 样本文件名\n",
    "    :param logs_file: 用户日志文件名\n",
    "    :param need_label: 是否要保留样本标签\n",
    "    :param out_file: 处理后的数据保存路径及文件名\n",
    "    :return: 处理后的数据\n",
    "    \"\"\"\n",
    "    \n",
    "    # 如果输出文件已存在，直接读取\n",
    "    if out_file and os.path.exists(out_file):\n",
    "        print(f\"使用缓存数据: {out_file}\")\n",
    "        return pd.read_csv(out_file)\n",
    "    \n",
    "    print(f\"处理数据: {samples_file}\")\n",
    "    \n",
    "    # 读取样本数据\n",
    "    samples = pd.read_csv(samples_file)\n",
    "    \n",
    "    # 特征工程 - 用户行为特征\n",
    "    user_logs = pd.read_csv(logs_file)\n",
    "    \n",
    "    # 用户在每个商家的行为次数\n",
    "    user_merchant_behavior = user_logs.groupby(['user_id', 'merchant_id']).size().reset_index(name='user_merchant_behavior')\n",
    "    \n",
    "    # 用户在每个商家的点击次数\n",
    "    user_merchant_click = user_logs[user_logs['action_type'] == 0].groupby(\n",
    "        ['user_id', 'merchant_id']).size().reset_index(name='user_merchant_click')\n",
    "    \n",
    "    # 用户在每个商家的购买次数\n",
    "    user_merchant_buy = user_logs[user_logs['action_type'] == 1].groupby(\n",
    "        ['user_id', 'merchant_id']).size().reset_index(name='user_merchant_buy')\n",
    "    \n",
    "    # 用户在每个商家的收藏次数\n",
    "    user_merchant_collect = user_logs[user_logs['action_type'] == 2].groupby(\n",
    "        ['user_id', 'merchant_id']).size().reset_index(name='user_merchant_collect')\n",
    "    \n",
    "    # 用户在每个商家的加购物车次数\n",
    "    user_merchant_cart = user_logs[user_logs['action_type'] == 3].groupby(\n",
    "        ['user_id', 'merchant_id']).size().reset_index(name='user_merchant_cart')\n",
    "    \n",
    "    # 合并特征\n",
    "    features = samples.merge(user_merchant_behavior, on=['user_id', 'merchant_id'], how='left')\n",
    "    features = features.merge(user_merchant_click, on=['user_id', 'merchant_id'], how='left')\n",
    "    features = features.merge(user_merchant_buy, on=['user_id', 'merchant_id'], how='left')\n",
    "    features = features.merge(user_merchant_collect, on=['user_id', 'merchant_id'], how='left')\n",
    "    features = features.merge(user_merchant_cart, on=['user_id', 'merchant_id'], how='left')\n",
    "    \n",
    "    # 填充缺失值\n",
    "    features.fillna(0, inplace=True)\n",
    "    \n",
    "    # 添加标签（如果需要）\n",
    "    if need_label:\n",
    "        features['label'] = samples['label']\n",
    "    \n",
    "    # 保存处理后的数据\n",
    "    if out_file:\n",
    "        features.to_csv(out_file, index=False)\n",
    "        print(f\"已保存处理后的数据到: {out_file}\")\n",
    "    \n",
    "    return features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "89282d59",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2. 特征工程函数\n",
    "def feature_engineering(data):\n",
    "    \"\"\"\n",
    "    添加比率特征\n",
    "    :param data: 输入数据\n",
    "    :return: 添加了特征的数据\n",
    "    \"\"\"\n",
    "    # 添加行为比率特征\n",
    "    data['click_ratio'] = data['user_merchant_click'] / (data['user_merchant_behavior'] + 1e-5)\n",
    "    data['buy_ratio'] = data['user_merchant_buy'] / (data['user_merchant_behavior'] + 1e-5)\n",
    "    data['collect_ratio'] = data['user_merchant_collect'] / (data['user_merchant_behavior'] + 1e-5)\n",
    "    data['cart_ratio'] = data['user_merchant_cart'] / (data['user_merchant_behavior'] + 1e-5)\n",
    "    \n",
    "    return data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "bd0751b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3. 模型训练函数\n",
    "def train_model(model, data, features, label='label', test_size=0.2):\n",
    "    \"\"\"\n",
    "    训练模型并返回模型和测试集AUC\n",
    "    :param model: 模型对象\n",
    "    :param data: 训练数据\n",
    "    :param features: 特征列表\n",
    "    :param label: 标签列名\n",
    "    :param test_size: 测试集比例\n",
    "    :return: 训练好的模型和测试集AUC\n",
    "    \"\"\"\n",
    "    # 划分训练集和测试集\n",
    "    X_train, X_test, y_train, y_test = train_test_split(\n",
    "        data[features], \n",
    "        data[label], \n",
    "        test_size=test_size, \n",
    "        random_state=42,\n",
    "        stratify=data[label]\n",
    "    )\n",
    "    \n",
    "    # 训练模型\n",
    "    model.fit(X_train, y_train)\n",
    "    \n",
    "    # 在测试集上预测\n",
    "    proba = model.predict_proba(X_test)[:, 1]\n",
    "    \n",
    "    # 计算AUC\n",
    "    auc_score = roc_auc_score(y_test, proba)\n",
    "    \n",
    "    return model, auc_score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "6e060cad",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 4. 预测函数\n",
    "def make_predictions(model, data, features, out_file=None):\n",
    "    \"\"\"\n",
    "    使用训练好的模型进行预测\n",
    "    :param model: 训练好的模型\n",
    "    :param data: 待预测数据\n",
    "    :param features: 特征列表\n",
    "    :param out_file: 输出文件路径\n",
    "    :return: 预测结果DataFrame\n",
    "    \"\"\"\n",
    "    # 进行预测\n",
    "    predictions = data[['user_id', 'merchant_id']].copy()\n",
    "    predictions['prob'] = model.predict_proba(data[features])[:, 1]\n",
    "    predictions['label'] = (predictions['prob'] > 0.5).astype(int)\n",
    "    \n",
    "    # 保存结果\n",
    "    if out_file:\n",
    "        predictions.to_csv(out_file, index=False)\n",
    "        print(f\"预测结果已保存到: {out_file}\")\n",
    "    \n",
    "    return predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "d7066212",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 5. 网格搜索函数\n",
    "def grid_search_model(estimator, param_grid, data, features, label='label'):\n",
    "    \"\"\"\n",
    "    执行网格搜索寻找最佳参数\n",
    "    :param estimator: 模型估计器\n",
    "    :param param_grid: 参数网格\n",
    "    :param data: 训练数据\n",
    "    :param features: 特征列表\n",
    "    :param label: 标签列名\n",
    "    :return: 最佳模型和最佳参数\n",
    "    \"\"\"\n",
    "    print(\"开始网格搜索...\")\n",
    "    # 创建GridSearchCV对象\n",
    "    grid_search = GridSearchCV(\n",
    "        estimator=estimator,\n",
    "        param_grid=param_grid,\n",
    "        cv=3,\n",
    "        scoring='roc_auc',\n",
    "        n_jobs=-1,\n",
    "        verbose=1\n",
    "    )\n",
    "    \n",
    "    # 执行网格搜索\n",
    "    grid_search.fit(data[features], data[label])\n",
    "    \n",
    "    # 输出最佳参数\n",
    "    print(f\"最佳参数: {grid_search.best_params_}\")\n",
    "    print(f\"最佳AUC: {grid_search.best_score_:.4f}\")\n",
    "    \n",
    "    return grid_search.best_estimator_, grid_search.best_params_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "1d3352ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 6. 主程序\n",
    "def main():\n",
    "    # 设置文件路径\n",
    "    data_dir = f'{cwd}/data/'\n",
    "    tmp_dir = f'{cwd}/tmp/'\n",
    "    \n",
    "    train_file = os.path.join(data_dir, 'train.csv')\n",
    "    test_file = os.path.join(data_dir, 'test_without_label.csv')\n",
    "    logs_file = os.path.join(data_dir, 'user_log_format2.csv')\n",
    "    \n",
    "    train_out_file = os.path.join(tmp_dir, 'data_train.csv')\n",
    "    test_out_file = os.path.join(tmp_dir, 'data_test.csv')\n",
    "    result_file = os.path.join(tmp_dir, 'final_predictions.csv')\n",
    "    \n",
    "    # 数据预处理\n",
    "    print(\"=\"*50)\n",
    "    print(\"开始数据预处理...\")\n",
    "    train_data = data_process(train_file, logs_file, need_label=True, out_file=train_out_file)\n",
    "    test_data = data_process(test_file, logs_file, need_label=False, out_file=test_out_file)\n",
    "    \n",
    "    # 特征工程\n",
    "    print(\"\\n进行特征工程...\")\n",
    "    train_data = feature_engineering(train_data)\n",
    "    test_data = feature_engineering(test_data)\n",
    "    \n",
    "    # 定义特征列表\n",
    "    features = [\n",
    "        'user_merchant_behavior', \n",
    "        'user_merchant_click', \n",
    "        'user_merchant_buy',\n",
    "        'user_merchant_collect', \n",
    "        'user_merchant_cart',\n",
    "        'click_ratio', \n",
    "        'buy_ratio', \n",
    "        'collect_ratio', \n",
    "        'cart_ratio'\n",
    "    ]\n",
    "    \n",
    "    # 确保所有特征都存在\n",
    "    for feature in features:\n",
    "        if feature not in train_data.columns:\n",
    "            train_data[feature] = 0\n",
    "        if feature not in test_data.columns:\n",
    "            test_data[feature] = 0\n",
    "    \n",
    "    # 训练梯度提升树模型\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"训练梯度提升树模型...\")\n",
    "    gb_model = GradientBoostingClassifier(random_state=42, n_estimators=100)\n",
    "    gb_model, gb_auc = train_model(gb_model, train_data, features)\n",
    "    print(f\"梯度提升树模型测试集AUC: {gb_auc:.4f}\")\n",
    "    \n",
    "    # 使用网格搜索优化决策树\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"使用网格搜索优化决策树...\")\n",
    "    dt_param_grid = {\n",
    "        'criterion': ['gini', 'entropy'],\n",
    "        'max_depth': [3, 5, 7],\n",
    "        'min_samples_split': [2, 5, 10],\n",
    "        'min_samples_leaf': [1, 2, 4]\n",
    "    }\n",
    "    best_dt, best_dt_params = grid_search_model(\n",
    "        DecisionTreeClassifier(random_state=42),\n",
    "        dt_param_grid,\n",
    "        train_data,\n",
    "        features\n",
    "    )\n",
    "    \n",
    "    # 使用网格搜索优化KNN\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"使用网格搜索优化KNN...\")\n",
    "    knn_param_grid = {\n",
    "        'n_neighbors': [3, 5, 7],\n",
    "        'weights': ['uniform', 'distance'],\n",
    "        'p': [1, 2]\n",
    "    }\n",
    "    best_knn, best_knn_params = grid_search_model(\n",
    "        KNeighborsClassifier(),\n",
    "        knn_param_grid,\n",
    "        train_data,\n",
    "        features\n",
    "    )\n",
    "    \n",
    "    # 使用网格搜索优化朴素贝叶斯\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"使用网格搜索优化朴素贝叶斯...\")\n",
    "    nb_param_grid = {\n",
    "        'var_smoothing': [1e-9, 1e-8, 1e-7]\n",
    "    }\n",
    "    best_nb, best_nb_params = grid_search_model(\n",
    "        GaussianNB(),\n",
    "        nb_param_grid,\n",
    "        train_data,\n",
    "        features\n",
    "    )\n",
    "    \n",
    "    # 训练Bagging集成模型\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"训练Bagging集成模型...\")\n",
    "    try:\n",
    "        # 尝试使用较新版本的参数名\n",
    "        bagging_model = BaggingClassifier(\n",
    "            estimator=best_dt,\n",
    "            n_estimators=50,\n",
    "            max_samples=0.8,\n",
    "            random_state=42,\n",
    "            n_jobs=-1\n",
    "        )\n",
    "    except TypeError:\n",
    "        # 回退到旧版本参数名\n",
    "        bagging_model = BaggingClassifier(\n",
    "            base_estimator=best_dt,\n",
    "            n_estimators=50,\n",
    "            max_samples=0.8,\n",
    "            random_state=42,\n",
    "            n_jobs=-1\n",
    "        )\n",
    "    \n",
    "    bagging_model, bagging_auc = train_model(bagging_model, train_data, features)\n",
    "    print(f\"Bagging模型测试集AUC: {bagging_auc:.4f}\")\n",
    "    \n",
    "    # 训练Stacking集成模型\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"训练Stacking集成模型...\")\n",
    "    base_learners = [\n",
    "        ('dt', best_dt),\n",
    "        ('knn', best_knn),\n",
    "        ('nb', best_nb)\n",
    "    ]\n",
    "    \n",
    "    stacking_model = StackingClassifier(\n",
    "        estimators=base_learners,\n",
    "        final_estimator=GradientBoostingClassifier(random_state=42, n_estimators=50),\n",
    "        n_jobs=-1\n",
    "    )\n",
    "    \n",
    "    stacking_model, stacking_auc = train_model(stacking_model, train_data, features)\n",
    "    print(f\"Stacking模型测试集AUC: {stacking_auc:.4f}\")\n",
    "    \n",
    "    # 选择最佳模型进行预测\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"模型性能比较:\")\n",
    "    model_performance = {\n",
    "        'GradientBoosting': gb_auc,\n",
    "        'Bagging': bagging_auc,\n",
    "        'Stacking': stacking_auc\n",
    "    }\n",
    "    \n",
    "    for model_name, auc in model_performance.items():\n",
    "        print(f\"{model_name}: {auc:.4f}\")\n",
    "    \n",
    "    best_model_name = max(model_performance, key=model_performance.get)\n",
    "    print(f\"\\n最佳模型: {best_model_name}, AUC: {model_performance[best_model_name]:.4f}\")\n",
    "    \n",
    "    if best_model_name == 'GradientBoosting':\n",
    "        best_model = gb_model\n",
    "    elif best_model_name == 'Bagging':\n",
    "        best_model = bagging_model\n",
    "    else:\n",
    "        best_model = stacking_model\n",
    "    \n",
    "    # 在测试集上进行预测\n",
    "    print(\"\\n\" + \"=\"*50)\n",
    "    print(\"在测试集上进行预测...\")\n",
    "    result = make_predictions(\n",
    "        best_model,\n",
    "        test_data,\n",
    "        features,\n",
    "        out_file=result_file\n",
    "    )\n",
    "    \n",
    "    # 显示部分预测结果\n",
    "    print(\"\\n预测结果样例:\")\n",
    "    print(result.head(10))\n",
    "    \n",
    "    print(f\"\\n预测完成! 结果已保存到 {result_file}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "914f2f80",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================================\n",
      "开始数据预处理...\n",
      "使用缓存数据: F:/H/H/tmp/data_train.csv\n",
      "使用缓存数据: F:/H/H/tmp/data_test.csv\n",
      "\n",
      "进行特征工程...\n",
      "\n",
      "==================================================\n",
      "训练梯度提升树模型...\n",
      "梯度提升树模型测试集AUC: 0.6069\n",
      "\n",
      "==================================================\n",
      "使用网格搜索优化决策树...\n",
      "开始网格搜索...\n",
      "Fitting 3 folds for each of 54 candidates, totalling 162 fits\n",
      "最佳参数: {'criterion': 'gini', 'max_depth': 7, 'min_samples_leaf': 1, 'min_samples_split': 10}\n",
      "最佳AUC: 0.6043\n",
      "\n",
      "==================================================\n",
      "使用网格搜索优化KNN...\n",
      "开始网格搜索...\n",
      "Fitting 3 folds for each of 12 candidates, totalling 36 fits\n",
      "最佳参数: {'n_neighbors': 7, 'p': 2, 'weights': 'uniform'}\n",
      "最佳AUC: 0.5248\n",
      "\n",
      "==================================================\n",
      "使用网格搜索优化朴素贝叶斯...\n",
      "开始网格搜索...\n",
      "Fitting 3 folds for each of 3 candidates, totalling 9 fits\n",
      "最佳参数: {'var_smoothing': 1e-07}\n",
      "最佳AUC: 0.6001\n",
      "\n",
      "==================================================\n",
      "训练Bagging集成模型...\n",
      "Bagging模型测试集AUC: 0.6057\n",
      "\n",
      "==================================================\n",
      "训练Stacking集成模型...\n",
      "Stacking模型测试集AUC: 0.6056\n",
      "\n",
      "==================================================\n",
      "模型性能比较:\n",
      "GradientBoosting: 0.6069\n",
      "Bagging: 0.6057\n",
      "Stacking: 0.6056\n",
      "\n",
      "最佳模型: GradientBoosting, AUC: 0.6069\n",
      "\n",
      "==================================================\n",
      "在测试集上进行预测...\n",
      "预测结果已保存到: F:/H/H/tmp/final_predictions.csv\n",
      "\n",
      "预测结果样例:\n",
      "   user_id  merchant_id      prob  label\n",
      "0    34176         3906  0.082277      0\n",
      "1    34176          121  0.048806      0\n",
      "2    34176         4356  0.074958      0\n",
      "3    34176         2217  0.043311      0\n",
      "4   230784         4818  0.048806      0\n",
      "5   362112         2618  0.048122      0\n",
      "6    34944         2051  0.045078      0\n",
      "7   231552         3828  0.197097      0\n",
      "8   231552         2124  0.054087      0\n",
      "9   232320         1168  0.052680      0\n",
      "\n",
      "预测完成! 结果已保存到 F:/H/H/tmp/final_predictions.csv\n"
     ]
    }
   ],
   "source": [
    "# 执行主程序\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79129e2b",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
